/* Texture Utilities */

// Sample Offsets
static const float2 g_SampleOffsets[] =
{
	float2(-1.0f, 0.0f),
	float2(1.0f, 0.0f),
	float2(0.0f, -1.0f),
	float2(0.0f, 1.0f),
};
// Sample Count
static const uint g_SampleCount = 4;

/* Bilinear Texture Sample. Type: float */
float BilinearSampleTexture(Texture2D<float> input, SamplerState textureSampler, float2 uv, uint mipLevel)
{
	float textureWidth = 0.0f;
	float textureHeight = 0.0f;
	uint numberOfLevels = 0;
	input.GetDimensions(mipLevel, textureWidth, textureHeight, numberOfLevels);

	float horizontalStep = 1.0f / (textureWidth - 1);
	float verticalStep = 1.0f / (textureHeight - 1);

	float deltaX = fmod(uv.x, horizontalStep);
	float deltaY = fmod(uv.y, verticalStep);

	float2 lowerLeftUv = uv;
	lowerLeftUv.x = uv.x - deltaX;
	lowerLeftUv.y = uv.y - deltaY;
	float lowerLeftSample = input.SampleLevel(textureSampler, lowerLeftUv, mipLevel);

	float2 lowerRightUv = uv;
	lowerRightUv.x = uv.x + (horizontalStep - deltaX);
	lowerRightUv.y = uv.y - deltaY;
	float lowerRightSample = input.SampleLevel(textureSampler, lowerRightUv, mipLevel);

	float2 upperLeftUv = uv;
	upperLeftUv.x = uv.x - deltaX;
	upperLeftUv.y = uv.y + (verticalStep - deltaY);
	float upperLeftSample = input.SampleLevel(textureSampler, upperLeftUv, mipLevel);

	float2 upperRightUv = uv;
	upperRightUv.x = uv.x + (horizontalStep - deltaX);
	upperRightUv.y = uv.y + (verticalStep - deltaY);
	float upperRightSample = input.SampleLevel(textureSampler, upperRightUv, mipLevel);

	float alphaX = deltaX / horizontalStep;
	float alphaY = deltaY / verticalStep;

	float upperSample = lerp(upperLeftSample, upperRightSample, alphaX);
	float lowerSample = lerp(lowerLeftSample, lowerRightSample, alphaX);

	float sample = lerp(lowerSample, upperSample, alphaY);

	return sample;
}

/* Bilinear Texture Sample. Type: float2 */
float2 BilinearSampleTexture(Texture2D<float2> input, SamplerState textureSampler, float2 uv, uint mipLevel)
{
	float textureWidth = 0.0f;
	float textureHeight = 0.0f;
	uint numberOfLevels = 0;
	input.GetDimensions(mipLevel, textureWidth, textureHeight, numberOfLevels);

	float horizontalStep = 1.0f / (textureWidth - 1);
	float verticalStep = 1.0f / (textureHeight - 1);

	float deltaX = fmod(uv.x, horizontalStep);
	float deltaY = fmod(uv.y, verticalStep);

	float2 lowerLeftUv = uv;
	lowerLeftUv.x = uv.x - deltaX;
	lowerLeftUv.y = uv.y - deltaY;
	float2 lowerLeftSample = input.SampleLevel(textureSampler, lowerLeftUv, mipLevel);

	float2 lowerRightUv = uv;
	lowerRightUv.x = uv.x + (horizontalStep - deltaX);
	lowerRightUv.y = uv.y - deltaY;
	float2 lowerRightSample = input.SampleLevel(textureSampler, lowerRightUv, mipLevel);

	float2 upperLeftUv = uv;
	upperLeftUv.x = uv.x - deltaX;
	upperLeftUv.y = uv.y + (verticalStep - deltaY);
	float2 upperLeftSample = input.SampleLevel(textureSampler, upperLeftUv, mipLevel);

	float2 upperRightUv = uv;
	upperRightUv.x = uv.x + (horizontalStep - deltaX);
	upperRightUv.y = uv.y + (verticalStep - deltaY);
	float2 upperRightSample = input.SampleLevel(textureSampler, upperRightUv, mipLevel);

	float alphaX = deltaX / horizontalStep;
	float alphaY = deltaY / verticalStep;

	float2 upperSample = lerp(upperLeftSample, upperRightSample, alphaX);
	float2 lowerSample = lerp(lowerLeftSample, lowerRightSample, alphaX);

	float2 sample = lerp(lowerSample, upperSample, alphaY);

	return sample;
}

/* Bilinear Texture Sample. Type: float3 */
float3 BilinearSampleTexture(Texture2D<float3> input, SamplerState textureSampler, float2 uv, uint mipLevel)
{
	float textureWidth = 0.0f;
	float textureHeight = 0.0f;
	uint numberOfLevels = 0;
	input.GetDimensions(mipLevel, textureWidth, textureHeight, numberOfLevels);

	float horizontalStep = 1.0f / (textureWidth - 1);
	float verticalStep = 1.0f / (textureHeight - 1);

	float deltaX = fmod(uv.x, horizontalStep);
	float deltaY = fmod(uv.y, verticalStep);

	float2 lowerLeftUv = uv;
	lowerLeftUv.x = uv.x - deltaX;
	lowerLeftUv.y = uv.y - deltaY;
	float3 lowerLeftSample = input.SampleLevel(textureSampler, lowerLeftUv, mipLevel);

	float2 lowerRightUv = uv;
	lowerRightUv.x = uv.x + (horizontalStep - deltaX);
	lowerRightUv.y = uv.y - deltaY;
	float3 lowerRightSample = input.SampleLevel(textureSampler, lowerRightUv, mipLevel);

	float2 upperLeftUv = uv;
	upperLeftUv.x = uv.x - deltaX;
	upperLeftUv.y = uv.y + (verticalStep - deltaY);
	float3 upperLeftSample = input.SampleLevel(textureSampler, upperLeftUv, mipLevel);

	float2 upperRightUv = uv;
	upperRightUv.x = uv.x + (horizontalStep - deltaX);
	upperRightUv.y = uv.y + (verticalStep - deltaY);
	float3 upperRightSample = input.SampleLevel(textureSampler, upperRightUv, mipLevel);

	float alphaX = deltaX / horizontalStep;
	float alphaY = deltaY / verticalStep;

	float3 upperSample = lerp(upperLeftSample, upperRightSample, alphaX);
	float3 lowerSample = lerp(lowerLeftSample, lowerRightSample, alphaX);

	float3 sample = lerp(lowerSample, upperSample, alphaY);

	return sample;
}

/* Bilinear Texture Sample. Type: float4 */
float4 BilinearSampleTexture(Texture2D<float4> input, SamplerState textureSampler, float2 uv, uint mipLevel)
{
	float textureWidth = 0.0f;
	float textureHeight = 0.0f;
	uint numberOfLevels = 0;
	input.GetDimensions(mipLevel, textureWidth, textureHeight, numberOfLevels);

	float horizontalStep = 1.0f / (textureWidth - 1);
	float verticalStep = 1.0f / (textureHeight - 1);

	float deltaX = fmod(uv.x, horizontalStep);
	float deltaY = fmod(uv.y, verticalStep);

	float2 lowerLeftUv = uv;
	lowerLeftUv.x = uv.x - deltaX;
	lowerLeftUv.y = uv.y - deltaY;
	float4 lowerLeftSample = input.SampleLevel(textureSampler, lowerLeftUv, mipLevel);

	float2 lowerRightUv = uv;
	lowerRightUv.x = uv.x + (horizontalStep - deltaX);
	lowerRightUv.y = uv.y - deltaY;
	float4 lowerRightSample = input.SampleLevel(textureSampler, lowerRightUv, mipLevel);

	float2 upperLeftUv = uv;
	upperLeftUv.x = uv.x - deltaX;
	upperLeftUv.y = uv.y + (verticalStep - deltaY);
	float4 upperLeftSample = input.SampleLevel(textureSampler, upperLeftUv, mipLevel);

	float2 upperRightUv = uv;
	upperRightUv.x = uv.x + (horizontalStep - deltaX);
	upperRightUv.y = uv.y + (verticalStep - deltaY);
	float4 upperRightSample = input.SampleLevel(textureSampler, upperRightUv, mipLevel);

	float alphaX = deltaX / horizontalStep;
	float alphaY = deltaY / verticalStep;

	float4 upperSample = lerp(upperLeftSample, upperRightSample, alphaX);
	float4 lowerSample = lerp(lowerLeftSample, lowerRightSample, alphaX);

	float4 sample = lerp(lowerSample, upperSample, alphaY);

	return sample;
}

/* Multi-Sample Texture. Type: float */
float MultiSampleTexture(Texture2D<float> input, SamplerState textureSampler, float2 uv, uint mipLevel, float2 offsetScale)
{
	float sample = 0.0f;
	[unroll]
	for (uint i = 0; i < g_SampleCount; ++i)
	{
		float2 nuv = uv + (g_SampleOffsets[i] * offsetScale);
		sample += BilinearSampleTexture(input, textureSampler, uv, mipLevel);
	}
	sample /= g_SampleCount;

	return sample;
}

/* Multi-Sample Texture. Type: float2 */
float2 MultiSampleTexture(Texture2D<float2> input, SamplerState textureSampler, float2 uv, uint mipLevel, float2 offsetScale)
{
	float2 sample = 0.0f;
	[unroll]
	for (uint i = 0; i < g_SampleCount; ++i)
	{
		float2 nuv = uv + (g_SampleOffsets[i] * offsetScale);
		sample += BilinearSampleTexture(input, textureSampler, uv, mipLevel);
	}
	sample /= g_SampleCount;

	return sample;
}

/* Multi-Sample Texture. Type: float3 */
float3 MultiSampleTexture(Texture2D<float3> input, SamplerState textureSampler, float2 uv, uint mipLevel, float2 offsetScale)
{
	float3 sample = 0.0f;
	[unroll]
	for (uint i = 0; i < g_SampleCount; ++i)
	{
		float2 nuv = uv + (g_SampleOffsets[i] * offsetScale);
		sample += BilinearSampleTexture(input, textureSampler, uv, mipLevel);
	}
	sample /= g_SampleCount;

	return sample;
}

/* Multi-Sample Texture. Type: float4 */
float4 MultiSampleTexture(Texture2D<float4> input, SamplerState textureSampler, float2 uv, uint mipLevel, float2 offsetScale)
{
	float4 sample = 0.0f;
	[unroll]
	for (uint i = 0; i < g_SampleCount; ++i)
	{
		float2 nuv = uv + (g_SampleOffsets[i] * offsetScale);
		sample += BilinearSampleTexture(input, textureSampler, uv, mipLevel);
	}
	sample /= g_SampleCount;

	return sample;
}